import java.io.*;
import java.net.*;
import java.util.*;
import java.util.logging.*;

import sharedClasses.Match;
import sharedClasses.Play;

public class Server
{
	private ServerSocket serverSocket; //the socket that the server listens to
	private Socket connection = null; //the connection between the client and server
	private ObjectOutputStream outStream; //the stream to send to the client
	private HackedObjectInputStream inStream; //the stream to receive from the client
	private DataIO dataIO; //the database handler
	private static final int QUEUE_LENGTH = 10; //number of passed object ids to remember for "TCP"
	private Queue<Integer> objectIds; //object id memory
	private static final Logger logger = Logger.getLogger("Server"); //logger for messages
	String message;
	
	//Makes a new server, does not begin listening though
	public Server()
	{
		try
		{
			FileHandler handler = new FileHandler("connection.log", true);
			handler.setFormatter(new SimpleFormatter());
			logger.addHandler(handler);
			logger.setLevel(Level.INFO);
			
			
			objectIds = new LinkedList<Integer>();
			dataIO = new DataIO();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	
	//Make a new server, and have it listen on its port. When the client disconnects, make a new server
	public static void main(String[] args)
	{
		while (true)
		{			
			Server s = new Server();
			s.start();
		}

	}
	
	//Start listening on the port
	private void start()
	{
	
		try
		{		
			//1. Create the socket
			serverSocket = new ServerSocket(48477);
			////System.out.println("Waiting for connection");
			logger.info("Waiting for connection");
			
			//2. Wait for a connection
			connection = serverSocket.accept();
			//System.out.println("Connected to " + connection.getInetAddress());
			logger.info("Connected to " + connection.getInetAddress());
			
			//3. Initialize streams
			outStream = new ObjectOutputStream(connection.getOutputStream());
			inStream = new HackedObjectInputStream(connection.getInputStream());
	
			do
			{
				try
				{
					Object o = inStream.readObject();
					if (o instanceof String)
					{
						message = (String) o;
						
						logger.info("Client: " + message);
						if (message.equals("analyze"))
						{
							Match temp = dataIO.getAllPlays_HACK(); //This is a hack that returns a match containing every play in the DB
																	//We're using it because we ran into some last minute issues with the way we were collecting plays alone
							Play[] plays = new Play[temp.children.size()];
							temp.children.toArray(plays);
							ArrayList<Play>[] patterns = new ArrayList[7];
							for (int i = 0; i < 7; i++)
							{
								patterns[i] = new ArrayList<Play>();
							}
							patterns[0].add(Engine.findStrongestPattern(plays));
							patterns[0].add(Engine.findWeakestPattern(plays));
							patterns[0].add(Engine.findOpponentServeReceivePatterns(plays));
							for (int i = 0; i < 6; i++)
							{
								temp = dataIO.getPlaysFromRotation_HACK(i+1);
								plays = new Play[temp.children.size()];
								temp.children.toArray(plays);
								patterns[i + 1].add(Engine.findStrongestPattern(plays));
								patterns[i + 1].add(Engine.findWeakestPattern(plays));
								patterns[i + 1].add(Engine.findOpponentServeReceivePatterns(plays));
							}
							
							Play[][] array = new Play[7][3];
							for (int j = 0; j < 7; j++)
							{
								for (int i = 0; i < 3; i++)
								{	
									array[j][i] = patterns[j].get(i);
								}
							}
							
							sendMessage(array);
							sendMessage("done");
						}
						else if (message.equals("timeout"))
						{
							Match temp = dataIO.lookUpMatch(dataIO.getCurrentMatch());
							Play[] plays = new Play[temp.children.size()];
							temp.children.toArray(plays);
							ArrayList<Play> patterns = new ArrayList<Play>();
							patterns.add(Engine.findStrongestPattern(plays));
							patterns.add(Engine.findWeakestPattern(plays));
							patterns.add(Engine.findOpponentServeReceivePatterns(plays));
							
							Play[] array = new Play[patterns.size()];
							for (int i = 0; i < patterns.size(); i++)
							{
								array[i] = (Play) patterns.get(i);
							}
							
							sendMessage(array);
							sendMessage("done");
							
						}
						else if (message.equals("review"))
						{
							
						}
						else if (message.equals("goodbye"))
						{
							break;
						}
						else
						{
							sendMessage("Error");
						}
					}
					else if (o instanceof Play)
					{
						Play temp = (Play) o;
						
						o = inStream.readObject();
						message = (String) o;
						if (message.equals("done"))
						{
//							if (!searchQueue(temp.getPlayID()))
//							{
								dataIO.addPlay(temp);
//								addToQueue(temp.getPlayID()); //THIS IS THE OBJECT-LEVEL TCP, IT WAS COMMENTED OUT AT THE LAST SECOND
															  //FOR DEBUGGING FOR THE DEMO. I don't believe it was the problem though
								sendInt(temp.getPlayID());
//							}
						}
						else
						{
							sendMessage("Error");
							message = "Error";
						}
					}
					else if (o instanceof Match)
					{
						Match temp = (Match) o;
						o = inStream.readObject();
						message = (String) o;
						if (message.equals("done"))
						{
//							if (!searchQueue(temp.getMatchID()))
//							{
								dataIO.addMatch(temp);
//								addToQueue(temp.getMatchID());
								sendInt(temp.getMatchID());
//							}
						}
						else
						{
							sendMessage("Error");
							message = "Error";
						}
					}
				}
				catch (ClassNotFoundException e)
				{
					e.printStackTrace();
				}
			} while (!message.equals("goodbye") && !message.equals("errror"));
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		finally
		{
			//5. Close the connection
			try
			{
				logger.info("Closing connection");
				outStream.close();
				inStream.close();
				connection.close();
				serverSocket.close();
				dataIO.close();
				objectIds.clear();
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}
	}
	
	//Send a message to the client
	private void sendMessage(Object message)
	{
		try
		{
			outStream.writeObject(message);
			outStream.flush();
			logger.info("Server: " + message);
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	
	//Send an int to the client
	private void sendInt(int message)
	{
		try
		{
			outStream.writeInt(message);
			outStream.flush();
			logger.info("Server: " + message);
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	
	//Add an id to the TCP memory
	private void addToQueue(int id)
	{
		if (objectIds.size() < QUEUE_LENGTH)
		{
			objectIds.add(id);
		}
		else
		{
			objectIds.poll();
			objectIds.add(id);
		}
	}
	
	//Check if an id is in the TCP memory
	private boolean searchQueue(int id)
	{
		return ((LinkedList<Integer>)objectIds).contains(id);
	}

}
